home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-9.10-netbook-remix-PL.iso / casper / filesystem.squashfs / usr / bin / apport-cli < prev    next >
Text File  |  2009-11-03  |  14KB  |  405 lines

  1. #!/usr/bin/python
  2.  
  3. '''Command line Apport user interface.
  4.  
  5. Copyright (C) 2007 Canonical Ltd.
  6. Author: Michael Hofmann <mh21@piware.de>
  7.  
  8. This program is free software; you can redistribute it and/or modify it
  9. under the terms of the GNU General Public License as published by the
  10. Free Software Foundation; either version 2 of the License, or (at your
  11. option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
  12. the full text of the license.
  13. '''
  14.  
  15. # Web browser support:
  16. #    w3m, lynx: do not work
  17. #    elinks: works
  18.  
  19. import os.path, os, sys, subprocess, re, errno
  20. import tty, termios, tempfile
  21. from datetime import datetime
  22.  
  23. from apport import unicode_gettext as _
  24. import apport.ui
  25.  
  26. class CLIDialog:
  27.     '''Command line dialog wrapper.'''
  28.  
  29.     def __init__(self, heading, text):
  30.         self.heading = '\n*** ' + heading + '\n'
  31.         self.text = text
  32.         self.keys = []
  33.         self.buttons = []
  34.         self.visible = False
  35.  
  36.     def raw_input_char(self, prompt):
  37.         '''raw_input, but read only one character'''
  38.  
  39.         print >> sys.stdout, prompt,
  40.  
  41.         file = sys.stdin.fileno()
  42.         saved_attributes = termios.tcgetattr(file)
  43.         attributes = termios.tcgetattr(file)
  44.         attributes[3] = attributes[3] & ~(termios.ICANON)
  45.         attributes[6][termios.VMIN] = 1
  46.         attributes[6][termios.VTIME] = 0
  47.         termios.tcsetattr(file, termios.TCSANOW, attributes)
  48.  
  49.         try:
  50.             ch = str(sys.stdin.read(1))
  51.         finally:
  52.             termios.tcsetattr(file, termios.TCSANOW, saved_attributes)
  53.  
  54.         print
  55.         return ch
  56.  
  57.     def show(self):
  58.         self.visible = True
  59.         print self.heading
  60.         if self.text:
  61.             print self.text
  62.  
  63.     def run(self, prompt=None):
  64.         if not self.visible:
  65.             self.show()
  66.  
  67.         print
  68.         try:
  69.             # Only one button
  70.             if len (self.keys) <= 1:
  71.                 self.raw_input_char(_('Press any key to continue...'))
  72.                 return 0
  73.             # Multiple choices
  74.             while True:
  75.                 if prompt is not None:
  76.                     print prompt
  77.                 else:
  78.                     print _('What would you like to do? Your options are:')
  79.                 for index, button in enumerate(self.buttons):
  80.                     print '  %s: %s' % (self.keys[index], button)
  81.  
  82.                 response = self.raw_input_char(_('Please choose (%s):') % ('/'.join(self.keys)))
  83.                 try:
  84.                     return self.keys.index(response[0].upper()) + 1
  85.                 except ValueError:
  86.                     pass
  87.         except KeyboardInterrupt:
  88.             print
  89.             sys.exit(1)
  90.  
  91.     def addbutton(self, button, hotkey=None):
  92.         if hotkey:
  93.             self.keys.append(hotkey)
  94.             self.buttons.append(button)
  95.         else:
  96.             self.keys.append(re.search('&(.)', button).group(1).upper())
  97.             self.buttons.append(re.sub('&', '', button))
  98.         return len(self.keys)
  99.  
  100.  
  101. class CLIProgressDialog(CLIDialog):
  102.     '''Command line progress dialog wrapper.'''
  103.  
  104.     def __init__(self, heading, text):
  105.         CLIDialog.__init__(self, heading, text)
  106.         self.progresscount = 0
  107.  
  108.     def set(self, progress = None):
  109.         self.progresscount = (self.progresscount + 1) % 5
  110.         if self.progresscount:
  111.             return
  112.  
  113.         if progress != None:
  114.             sys.stdout.write('\r%u%%' % (progress * 100))
  115.         else:
  116.             sys.stdout.write('.')
  117.         sys.stdout.flush()
  118.  
  119. class CLIUserInterface(apport.ui.UserInterface):
  120.     '''Command line Apport user interface'''
  121.  
  122.     def __init__(self):
  123.         apport.ui.UserInterface.__init__(self)
  124.  
  125.     #
  126.     # ui_* implementation of abstract UserInterface classes
  127.     #
  128.  
  129.     def ui_present_crash(self, desktop_entry):
  130.         date = datetime.strptime(self.report['Date'], '%a %b %d %H:%M:%S %Y')
  131.         # adapt dialog heading and label appropriately
  132.         if desktop_entry:
  133.             name = desktop_entry.getName()
  134.         elif self.report.has_key('ExecutablePath'):
  135.             name = os.path.basename(self.report['ExecutablePath'])
  136.         else:
  137.             name = self.cur_package
  138.         # translators: first %s: application name, second %s: date, third %s: time
  139.         heading = _('%s closed unexpectedly on %s at %s.') % (name, date.date(), date.time())
  140.  
  141.         dialog = CLIDialog(
  142.                 heading,
  143.                 _('If you were not doing anything confidential (entering passwords or other\n'
  144.                   'private information), you can help to improve the application by reporting\n'
  145.                   'the problem.'))
  146.         report = dialog.addbutton(_('&Report Problem...'))
  147.         ignore = dialog.addbutton(_('Cancel and &ignore future crashes of this program version'))
  148.         dialog.addbutton(_('&Cancel'))
  149.  
  150.         # show crash notification dialog
  151.         response = dialog.run()
  152.  
  153.         if response == report:
  154.             return {'action': 'report', 'blacklist': False}
  155.         if response == ignore:
  156.             return {'action': 'cancel', 'blacklist': True}
  157.         # Fallback
  158.         return {'action': 'cancel', 'blacklist': False}
  159.  
  160.     def ui_present_package_error(self):
  161.         name = self.report['Package']
  162.         dialog = CLIDialog(
  163.                 _('The package "%s" failed to install or upgrade.') % name,
  164.                 _('You can help the developers to fix the package by reporting the problem.'))
  165.         report = dialog.addbutton(_('&Report Problem...'))
  166.         dialog.addbutton(_('&Cancel'))
  167.  
  168.         response = dialog.run()
  169.  
  170.         if response == report:
  171.             return 'report'
  172.         # Fallback
  173.         return 'cancel'
  174.  
  175.     def ui_present_kernel_error(self):
  176.         message = _('Your system encountered a serious kernel problem.')
  177.         annotation = ''
  178.         if self.report.has_key('Annotation'):
  179.             annotation += self.report['Annotation'] + '\n\n'
  180.         annotation += _('You can help the developers to fix the problem by reporting it.')
  181.  
  182.         dialog = CLIDialog (message, annotation)
  183.         report = dialog.addbutton(_('&Report Problem...'))
  184.         dialog.addbutton(_('&Cancel'))
  185.  
  186.         response = dialog.run()
  187.  
  188.         if response == report:
  189.             return 'report'
  190.         # Fallback
  191.         return 'cancel'
  192.  
  193.     def ui_present_report_details(self):
  194.         dialog = CLIDialog (
  195.                 _('Send problem report to the developers?'),
  196.                 _('After the problem report has been sent, please fill out the form in the\n'
  197.                   'automatically opened web browser.'))
  198.  
  199.         # report contents
  200.         details = ''
  201.         for key in self.report:
  202.             details += key + ':'
  203.             # string value
  204.             if not hasattr(self.report[key], 'gzipvalue') and \
  205.                 hasattr(self.report[key], 'isspace') and \
  206.                 not self.report._is_binary(self.report[key]):
  207.                 lines = self.report[key].splitlines()
  208.                 if len(lines) <= 1:
  209.                     details += ' ' + self.report[key] + '\n'
  210.                 else:
  211.                     details += '\n'
  212.                     for line in lines:
  213.                         details += '  ' + line + '\n'
  214.             else:
  215.                 details += ' ' + _('(binary data)') + '\n'
  216.  
  217.         # complete/reduced reports
  218.         if self.report.has_key('CoreDump') and self.report.has_useful_stacktrace():
  219.             complete = dialog.addbutton(_('&Send complete report (recommended; %s)') %
  220.                     self.format_filesize(self.get_complete_size()))
  221.             reduced = dialog.addbutton(_('Send &reduced report (slow Internet connection; %s)') %
  222.                     self.format_filesize(self.get_reduced_size()))
  223.         else:
  224.             complete = dialog.addbutton(_('&Send report (%s)') %
  225.                     self.format_filesize(self.get_complete_size()))
  226.             reduced = None
  227.  
  228.         view = dialog.addbutton(_('&View report'))
  229.         save = dialog.addbutton(_('&Keep report file for sending later or copying to somewhere else'))
  230.         
  231.         dialog.addbutton(_('&Cancel'))
  232.  
  233.         while True:
  234.             response = dialog.run()
  235.  
  236.             if response == complete:
  237.                 return 'full'
  238.             if response == reduced:
  239.                 return 'reduced'
  240.             if response == view:
  241.                 try:
  242.                     subprocess.Popen(["/usr/bin/sensible-pager"],
  243.                             stdin=subprocess.PIPE,
  244.                             close_fds=True).communicate(details)
  245.                 except IOError, e:
  246.                     # ignore broken pipe (premature quit)
  247.                     if e.errno == errno.EPIPE:
  248.                         pass
  249.                     else:
  250.                         raise
  251.                 continue
  252.             if response == save:
  253.                 # we do not already have a report file if we report a bug
  254.                 if not self.report_file:
  255.                     prefix = 'apport.'
  256.                     if self.report.has_key('Package'):
  257.                         prefix += self.report['Package'].split()[0] + '.'
  258.                     (fd, self.report_file) = tempfile.mkstemp(prefix=prefix, suffix='.apport')
  259.                     self.report.write(os.fdopen(fd, 'w'))
  260.  
  261.                 print _('Problem report file:'), self.report_file
  262.                 return 'cancel'
  263.  
  264.             # Fallback
  265.             return 'cancel'
  266.  
  267.     def ui_info_message(self, title, text):
  268.         dialog = CLIDialog(title, text)
  269.         dialog.addbutton(_('&Confirm'))
  270.         dialog.run()
  271.  
  272.     def ui_error_message(self, title, text):
  273.         dialog = CLIDialog(_('Error: %s') % title, text)
  274.         dialog.addbutton(_('&Confirm'))
  275.         dialog.run()
  276.  
  277.     def ui_start_info_collection_progress(self):
  278.         self.progress = CLIProgressDialog (
  279.                 _('Collecting problem information'),
  280.                 _('The collected information can be sent to the developers to improve the\n'
  281.                   'application. This might take a few minutes.'))
  282.         self.progress.show()
  283.  
  284.     def ui_pulse_info_collection_progress(self):
  285.         self.progress.set()
  286.  
  287.     def ui_stop_info_collection_progress(self):
  288.         print
  289.  
  290.     def ui_start_upload_progress(self):
  291.         self.progress = CLIProgressDialog (
  292.                 _('Uploading problem information'),
  293.                 _('The collected information is being sent to the bug tracking system.\n'
  294.                   'This might take a few minutes.'))
  295.         self.progress.show()
  296.  
  297.     def ui_set_upload_progress(self, progress):
  298.         self.progress.set(progress)
  299.  
  300.     def ui_stop_upload_progress(self):
  301.         print
  302.  
  303.     def ui_question_yesno(self, text):
  304.         '''Show a yes/no question.
  305.  
  306.         Return True if the user selected "Yes", False if selected "No" or
  307.         "None" on cancel/dialog closing.
  308.         '''
  309.         dialog = CLIDialog(text, None)
  310.         r_yes = dialog.addbutton('&Yes')
  311.         r_no = dialog.addbutton('&No')
  312.         r_cancel = dialog.addbutton(_('&Cancel'))
  313.         result = dialog.run()
  314.         if result == r_yes:
  315.             return True
  316.         if result == r_no:
  317.             return False
  318.         assert result == r_cancel
  319.         return None
  320.  
  321.     def ui_question_choice(self, text, options, multiple):
  322.         '''Show an question with predefined choices.
  323.  
  324.         options is a list of strings to present. If multiple is True, they
  325.         should be check boxes, if multiple is False they should be radio
  326.         buttons.
  327.  
  328.         Return list of selected option indexes, or None if the user cancelled.
  329.         If multiple == False, the list will always have one element.
  330.         '''
  331.         result = []
  332.         dialog = CLIDialog(text, None)
  333.  
  334.         if multiple:
  335.             while True:
  336.                 dialog = CLIDialog(text, None)
  337.                 index = 0
  338.                 choice_index_map = {}
  339.                 for option in options:
  340.                     if index not in result:
  341.                         choice_index_map[dialog.addbutton(option, str(index+1))] = index
  342.                     index += 1
  343.                 done = dialog.addbutton(_('&Done'))
  344.                 cancel = dialog.addbutton(_('&Cancel'))
  345.  
  346.                 if result:
  347.                     cur = ', '.join([str(r+1) for r in result])
  348.                 else:
  349.                     cur = _('none')
  350.                 response = dialog.run(_('Selected: %s. Multiple choices:') % cur)
  351.                 if response == cancel:
  352.                     return None
  353.                 if response == done:
  354.                     break
  355.                 result.append(choice_index_map[response])
  356.  
  357.         else:
  358.             # single choice (radio button)
  359.             dialog = CLIDialog(text, None)
  360.             index = 1
  361.             for option in options:
  362.                 dialog.addbutton(option, str(index))
  363.                 index += 1
  364.  
  365.             cancel = dialog.addbutton(_('&Cancel'))
  366.             response = dialog.run(_('Choices:'))
  367.             if response == cancel:
  368.                 return None
  369.             result.append(response-1)
  370.  
  371.         return result
  372.  
  373.     def ui_question_file(self, text):
  374.         '''Show a file selector dialog.
  375.  
  376.         Return path if the user selected a file, or None if cancelled.
  377.         '''
  378.         print '\n*** ', text
  379.         while True:
  380.             print _('Path to file (Enter to cancel):'), ' ',
  381.             f = sys.stdin.readline().strip()
  382.             if not f:
  383.                 return None
  384.             if not os.path.exists(f):
  385.                 print _('File does not exist.')
  386.             elif os.path.isdir(f):
  387.                 print _('This is a directory.')
  388.             else:
  389.                 return f
  390.  
  391.     def open_url(self, url):
  392.         text = '%s\n\n  %s\n\n%s' % (
  393.             _('To continue, you must visit the following URL:'),
  394.             url,
  395.             _('You can launch a browser now, or copy this URL into a browser on another computer.'))
  396.  
  397.         answer = self.ui_question_choice(text, [_('Launch a browser now')], False)
  398.     if answer == [0]:
  399.             apport.ui.UserInterface.open_url(self, url)
  400.  
  401. if __name__ == '__main__':
  402.     app = CLIUserInterface()
  403.     if not app.run_argv():
  404.         print >> sys.stderr, _('No pending crash reports. Try --help for more information.')
  405.